package it.crosato.stage.server.model.retriever;

import it.crosato.stage.shared.exceptions.DatabaseConnectionException;
import it.crosato.stage.shared.exceptions.NonExistentException;
import it.crosato.stage.shared.exceptions.RetrievingException;
import it.crosato.stage.shared.objects.Pathway;
import it.crosato.stage.shared.objects.Reaction;

import java.rmi.RemoteException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.rpc.ServiceException;

import keggapi.KEGGLocator;
import keggapi.KEGGPortType;

public class PathwayRetriever implements IPathwayRetriever {

	private IReactionRetriever reactionRetriever = new ReactionRetriever();

	/**
	 * Recupera una via metabolica gestendo internamente le interazioni
	 * con la banca dati interna e KEGG.
	 * @param id il codice della via metabolica generica
	 * @param organism il codice dell'organismo
	 * @param time tempo di scadenza nella banca dati interna
	 * @return la via metabolica recuperata con tutte le sue informazioni
	 * @throws NonExistentException se la via metabolica non esiste
	 * @throws RetrievingException se si verificano dei problemi nel recupero
	 */
	@Override
	public Pathway getPathway(String id, String organism, long time) throws NonExistentException,
				RetrievingException {
		
		id = id.replace("map", organism);
		Logger.getLogger("myLog").info("recupero pathway " + id);
		Pathway pathway;
		try {
			Vector<String> reactionsIds = new Vector<String>();
			pathway = searchInDB(id, time, reactionsIds);
			if (pathway == null) {
				pathway = searchInKEGG(id, time, reactionsIds);
				if (pathway == null) {
					throw new NonExistentException(NonExistentException.PATHWAY);
				}
				storeInDB(pathway, reactionsIds);
			}
			pathway.setReactions(reactionRetriever.getPathwayReactions(id, reactionsIds,
					pathway.getEnzymes(), time));
			return pathway;
		}
		catch(DatabaseConnectionException dbce) {
			Logger.getLogger("myLog").info("dbce ---> " + dbce.getMessage());
			throw new RetrievingException(RetrievingException.PATHWAY);
		}
		catch(RetrievingException rete) {
			Logger.getLogger("myLog").info("rete ---> " + rete.getMessage());
			throw new RetrievingException(RetrievingException.PATHWAY);
		}
		catch (NonExistentException nere) {
			Logger.getLogger("myLog").info("nere ---> " + nere.getMessage());
			throw new RetrievingException(RetrievingException.PATHWAY);
		}
		catch (SQLException sqle) {
			Logger.getLogger("myLog").info("sqle ---> " + sqle.getMessage());
			throw new RetrievingException(RetrievingException.PATHWAY);
		}
		catch (RemoteException re) {
			Logger.getLogger("myLog").info("re ---> " + re.getMessage());
			throw new RetrievingException(RetrievingException.PATHWAY);
		}
		catch (ServiceException se) {
			Logger.getLogger("myLog").info("se ---> " + se.getMessage());
			throw new RetrievingException(RetrievingException.PATHWAY);
		}
		finally {
			DatabaseConnection.closeConnection();
		}
	}

	/**
	 * Cerca la via metabolica nella banca dati interna. Utilizza la classe ReactionRetriever
	 * per il recupero delle reazioni (eventualmente anche da KEGG).
	 * @param id codice della via metabolica
	 * @param time tempo di scadenza nella banca dati interna
	 * @return la via metabolica con tutte le sue informazioni oppure il valore nullo se essa
	 * non è presente nella banca dati interna o se è troppo vecchia
	 * @throws RetrievingException se si verificano problemi nel recupero
	 * @throws NonExistentException se la via metabolica non esiste
	 * @throws DatabaseConnectionException se si verificano problemi nella connessione
	 * alla banca dati interna
	 * @throws SQLException se si verificano problemi nelle query
	 */
	private Pathway searchInDB(String id, long time, Vector<String> reactionIds) throws RetrievingException,
				NonExistentException, DatabaseConnectionException, SQLException {
		
		DatabaseConnection.openConnection();
		Statement st = DatabaseConnection.getStatement();
		ResultSet rs = st.executeQuery("SELECT count(*) FROM Pathways WHERE id='" + id + "';");
		rs.next();
		if (rs.getInt(1) != 0) {
			Logger.getLogger("myLog").info("via metabolica presente nel database");
			rs = st.executeQuery("SELECT * FROM Pathways WHERE id='" + id + "';");
			rs.next();
			Long date = rs.getLong("storeDate");
			if (date + time < new Date().getTime()) {
				Logger.getLogger("myLog").info("via metabolica troppo vecchia.");
				return null;
			}
			String description = rs.getString("description");
			String name = rs.getString("name");
			String url = rs.getString("url");
			List<String> list = Arrays.asList(rs.getString("reactions").split(" "));
			reactionIds.addAll(new Vector<String>(list));
			list = Arrays.asList(rs.getString("enzymes").split(" "));
			Vector<String> enzymes = new Vector<String>(list);
			Vector<Reaction> reactions = new Vector<Reaction>();
			Logger.getLogger("myLog").info("recuperata via metabolica dal database");
			return new Pathway(id, name, description, url, reactions, enzymes);
		}
		else {
			Logger.getLogger("myLog").info("via metabilica non presente nel database");
			return null;
		}
	}

	/**
	 * Cerca la via metabolica in KEGG. Utilizza la classe ReactionRetriever per recuperare le
	 * reazioni chimiche (eventualmente anche dalla banca dati interna).
	 * @param id codice della via metabolica
	 * @param time tempo di scadenza nella banca dati interna
	 * @return la via metabolica con tutte le sue informazioni oppure il valore nullo se essa non
	 * esiste
	 * @throws ServiceException se si verificano problemi legati al servizio di KEGG.
	 * @throws RemoteException se si verificano problemi con l'interazione remota con KEGG.
	 * @throws NonExistentException se una delle reazioni non esiste.
	 * @throws RetrievingException se si verifcano problemi nel recupero.
	 */
	private Pathway searchInKEGG(String id, long time, Vector<String> reactionIds) throws ServiceException, RemoteException,
				NonExistentException, RetrievingException {

		Logger.getLogger("myLog").info("recupero via metabolica da KEGG");
		
		KEGGLocator locator = new KEGGLocator();
		KEGGPortType serv = locator.getKEGGPort();
		
		String pathwayString = serv.bget(id);
		if ( ! pathwayString.equals("")) {
			// nome
			Pattern pattern = Pattern.compile("NAME[\\s]*([^\\r\\n]*)");
			Matcher matcher = pattern.matcher(pathwayString);
			matcher.find();
			String name = matcher.group(1);
			// descrizione
			pattern = Pattern.compile("DESCRIPTION[\\s]*([^\\r\\n]*)");
			matcher = pattern.matcher(pathwayString);
			String description;
			if (matcher.find()) {
				description = matcher.group(1);
			}
			else {
				description = "Descrizione non disponibile";
			}
			// url immagine
			String url = serv.mark_pathway_by_objects(id, new String[0]);
			// enzimi
			List<String> list = Arrays.asList(serv.get_enzymes_by_pathway(id));
			Vector<String> enzymes = new Vector<String>(list);
			// reazioni
			list = Arrays.asList(serv.get_reactions_by_pathway(id));
			reactionIds.addAll(new Vector<String>(list));
			Vector<Reaction> reactions = new Vector<Reaction>();
			Logger.getLogger("myLog").info("via metabolica recuperata da KEGG");
			return new Pathway(id, name, description, url, reactions, enzymes);
		}
		else {
			Logger.getLogger("myLog").info("via metabolica non presente in KEGG");
			return null;
		}
	}

	/**
	 * Memorizza nella banca dati interna la via metabolica recuperata da KEGG, eventualmente
	 * sostituendo versioni precedenti.
	 * @param pathway la via metabolica con tutte le sue informazioni
	 * @throws DatabaseConnectionException se si verificano problemi nella connessione
	 * con la banca dati interna
	 */
	private void storeInDB(Pathway pathway, Vector<String> reactionIds) throws DatabaseConnectionException {
		
		Logger.getLogger("myLog").info("inserimento via metabolica nel database");
		DatabaseConnection.openConnection();
		Statement st = DatabaseConnection.getStatement();
		try {
			try {
				st.executeQuery("DELETE FROM Pathways WHERE id='" + pathway.getId() + "';");
			} catch(SQLException e) { }
			String reactionsStr = "";
			for (int i = 0; i < reactionIds.size(); i++) {
				reactionsStr += reactionIds.get(i);
				if (i < reactionIds.size() - 1) {
					reactionsStr += " ";
				}
			}
			String enzymesStr = "";
			for (int i = 0; i < pathway.getEnzymes().size(); i++) {
				enzymesStr += pathway.getEnzymes().get(i);
				if (i < pathway.getEnzymes().size() - 1) {
					enzymesStr += " ";
				}
			}
			st.executeUpdate("INSERT INTO Pathways VALUES('" + pathway.getId() + "', '" +  
					pathway.getName() + "', '" + pathway.getDescription() + "', '" +
					pathway.getUrl() + "', '" + reactionsStr + "', '" + enzymesStr +
					"', '" + new Date().getTime() + "');");
			Logger.getLogger("myLog").info("inserita via metabolica nel database");
		}
		catch (SQLException sqle) {
			Logger.getLogger("myLog").info("sqle --> " + sqle.toString());
		}
	}
}